package com.talentech.xproberlib;

import static com.talentech.xproberlib.Utils.log;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.ViewGroup;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;

public class HookAgent extends XC_MethodHook
{

	private static final String TAG = "XProber";
	static List<String> hookedClassList = new LinkedList<String>();

	protected void beforeHookedMethod(MethodHookParam param) throws Throwable
	{
	}

	protected void afterHookedMethod(MethodHookParam param) throws Throwable
	{
		String className = param.args[0].toString();
		Class<?> clazz = (Class<?>) (param.getResult());

		if (clazz == null)
		{
			// log("ClassLoader: class " + className + " not found!");
			return;
		}

		if (clazz.getPackage().getName().equals("com.talentech.xproberlib"))
		{
			return;
		}

		if (clazz.getPackage().getName().startsWith("de.robv.android.xposed"))
		{
			return;
		}
		
		

//		if ("android.support.v4.view.ViewPager".equals(clazz.getName()))
//		{
//			log("!! hooked ViewPager.initViewPager()");
//			XposedHelpers.findAndHookConstructor(ViewPager.class, Context.class, Hook.printName);
//			XposedHelpers.findAndHookConstructor(ViewPager.class, Context.class, AttributeSet.class, Hook.printName);
//		}
//
//		hookClassAndParent(clazz, Fragment.class, NameJudge.onXXX);
//		hookClassByName(clazz, "ViewPager", NameJudge.onXXX);
//		hookClassByName(clazz, "FragmentPagerAdapter", NameJudge.onXXX);
		
		hookActivityLifeCircle(clazz);
		
	//	hookConstructorByName(clazz, "Game");
	//	hookConstructorByName(clazz, "RPG");
			
		//hookClassAndParent(clazz,View.class,NameJudge.onXXX);
		//hookClassByName(clazz, "Game", NameJudge.all);
		//hookFragmentLifeCircle(clazz);
		//hookClassAndParent(clazz,Fragment.class,NameJudge.onXXX);
		hookOnConfigurationChanged(clazz);
	}

	private void hookConstructorByName(Class<?> clazz, String string) {
		// TODO 自动生成的方法存根
		if (clazz.getSimpleName().equals(string))
		{
	        XposedBridge.hookAllConstructors(clazz, Hook.printName);
			log("hooked " + clazz.getName() + "Constructor");
		}
	}

	protected void hookGesture(Class<?> clazz) throws NoSuchMethodException
	{
		if (!Activity.class.isAssignableFrom(clazz))
		{
			return;
		}

		XposedHelpers.findAndHookMethod(Activity.class, "dispatchTouchEvent", MotionEvent.class, GestureHooks.dispatchTouchEventHook);
	}

	protected void hookActivityLifeCircle(Class<?> clazz) throws NoSuchMethodException
	{
		if (!Activity.class.isAssignableFrom(clazz))
		{
			return;
		}

		XposedHelpers.findAndHookMethod(Activity.class, "startActivityForResult", Intent.class, int.class, Bundle.class, Hook.printName_Tabbed);
		XposedHelpers.findAndHookMethod(Activity.class, "startActivityForResult", Intent.class, int.class, Bundle.class,
				ActivityHooks.startActivityHook);
		XposedHelpers.findAndHookMethod(Activity.class, "onCreate", Bundle.class, ActivityHooks.onCreateHook);
		XposedHelpers.findAndHookMethod(Activity.class, "onConfigurationChanged", Configuration.class, Hook.printName);
		XposedHelpers.findAndHookMethod(Activity.class, "onCreate", Bundle.class, Hook.printName_Tabbed);
		XposedHelpers.findAndHookMethod(Activity.class, "onRestart", Hook.printName_Tabbed);
		XposedHelpers.findAndHookMethod(Activity.class, "onStart", Hook.printName_Tabbed);
		XposedHelpers.findAndHookMethod(Activity.class, "onResume", Hook.printName_Tabbed);
		XposedHelpers.findAndHookMethod(Activity.class, "onPause", Hook.printName_Tabbed);
		XposedHelpers.findAndHookMethod(Activity.class, "onStop", Hook.printName_Tabbed);
		XposedHelpers.findAndHookMethod(Activity.class, "onDestroy", Hook.printName_Tabbed);

		XposedHelpers.findAndHookMethod(Activity.class, "onSaveInstanceState", Bundle.class, Hook.printName_Tabbed);
		XposedHelpers.findAndHookMethod(Activity.class, "onRestoreInstanceState", Bundle.class, Hook.printName_Tabbed);

		log("-");
	}

	protected void hookFragmentLifeCircle(Class<?> clazz) throws NoSuchMethodException
	{
		if (!Fragment.class.isAssignableFrom(clazz))
		{
			return;
		}



		XposedHelpers.findAndHookMethod(Fragment.class, "onCreate", Bundle.class, Hook.printName_Tabbed);

		XposedHelpers.findAndHookMethod(Fragment.class, "onStart", Hook.printName_Tabbed);
		
		XposedHelpers.findAndHookMethod(Fragment.class, "onResume", Hook.printName_Tabbed);
		XposedHelpers.findAndHookMethod(Fragment.class, "onPause", Hook.printName_Tabbed);
		XposedHelpers.findAndHookMethod(Fragment.class, "onStop", Hook.printName_Tabbed);
		XposedHelpers.findAndHookMethod(Fragment.class, "onDestroy", Hook.printName_Tabbed);

		XposedHelpers.findAndHookMethod(Fragment.class, "onSaveInstanceState", Bundle.class, Hook.printName_Tabbed);
		XposedHelpers.findAndHookMethod(Fragment.class, "onRestoreInstanceState", Bundle.class, Hook.printName_Tabbed);

		
		XposedHelpers.findAndHookMethod(Fragment.class, "onAttach", Activity.class, Hook.printName_Tabbed);
		
		XposedHelpers.findAndHookMethod(Fragment.class, "onDetach", Hook.printName_Tabbed);
		
		XposedHelpers.findAndHookMethod(Fragment.class, "onCreateView", LayoutInflater.class, ViewGroup.class,
	            Bundle.class, Hook.printName_Tabbed);
		
		XposedHelpers.findAndHookMethod(Fragment.class, "onDestroyView", Hook.printName_Tabbed);
		log("-");
	}
	
	protected Method[] getMethodsFirstDeclaredHere(Class clazz)
	{
		Method[] methods = clazz.getDeclaredMethods();// clazz.getDeclaredMethods()

		// 筛选仅在本类新定义的方法------------------------------------
		List<Method> methodList = Arrays.asList(methods);
		List<Method> superMethodList = new LinkedList<Method>();

		Class<?> workingSuperClass = clazz.getSuperclass();
		while (workingSuperClass != null && !workingSuperClass.equals(Object.class))
		{
			log("Super class: " + workingSuperClass);
			superMethodList.addAll(Arrays.asList(workingSuperClass.getDeclaredMethods()));
			workingSuperClass = workingSuperClass.getSuperclass();
		}

		List<Method> newMethodList = new LinkedList<Method>();
		boolean isMethodDeclared = false;
		for (Method workingMethod : methodList)
		{
			for (Method supreWorkingMethod : superMethodList)
			{
				if (workingMethod.getName().equals(supreWorkingMethod.getName())) // 不严谨，仅名称检测
				{
					isMethodDeclared = true;
					break;
				}
			}

			if (!isMethodDeclared)
			{
				if (!Modifier.isAbstract(workingMethod.getModifiers()))
				{
					newMethodList.add(workingMethod);
				}
			}
			else
			{
				log("method " + workingMethod.getName() + " is already declared by super");
			}
			isMethodDeclared = false;
		}

		methods = (Method[]) newMethodList.toArray(new Method[newMethodList.size()]);
		return methods;
	}

	protected void hookMethod(Class<?> clazz, String target, NameJudger nameJudger)
	{

		log("hooked " + clazz.getName() + " ." + target + "()");
		XposedHelpers.findAndHookMethod(clazz, target, Hook.printName);
		
	}
		
	protected void hookClassByName(Class<?> clazz, String target, NameJudger nameJudger)
	{
		if (target.equals(clazz.getSimpleName()))
		{
			hookClass(clazz, clazz, nameJudger);
			//hookMethods(clazz, clazz, nameJudger);
		}
	}
	
	protected void hookClass(Class<?> clazz, Class<?> target, NameJudger nameJudger)
	{
		if (target.isAssignableFrom(clazz))
		{
			Method[] methods = clazz.getDeclaredMethods();

			for (Method method : methods)
			{
				if (!nameJudger.judge(method.getName()))
				{
					continue;
				}

				XposedBridge.hookMethod(method, Hook.printName);
				log("hooked " + clazz.getName() + " ." + method.getName() + "()");
			}
			log("-");
		}
	}

	protected void hookClass_OnlyDeclaredHere(Class<?> clazz, Class<?> target, NameJudger nameJudger)
	{
		if (target.isAssignableFrom(clazz))
		{
			Method[] methods = getMethodsFirstDeclaredHere(clazz);

			for (Method method : methods)
			{
				if (!nameJudger.judge(method.getName()))
				{
					continue;
				}

				XposedBridge.hookMethod(method, Hook.printName);
				log("hooked " + clazz.getName() + " ." + method.getName() + "()");
			}
			log("-");
		}
	}

	protected void hookClassAndParent(Class<?> clazz, Class<?> target, NameJudger nameJudger)
	{
		if (target.isAssignableFrom(clazz))
		{
			Class<?> workClass = clazz;

			while (true)
			{
				if (!target.isAssignableFrom(workClass))
				{
					break;
				}

				if (workClass == Object.class)
				{
					break;
				}

				boolean isHooked = false;
				for (String aHookedClass : hookedClassList)
				{
					if (aHookedClass.equals(workClass.getName()))
					{
						isHooked = true;
						break;
					}
				}
				if (isHooked)
				{
					log(workClass.getName() + " is allready hooked");
					break;
				}

				Method[] methods = workClass.getDeclaredMethods();// clazz.getDeclaredMethods()
				for (Method method : methods)
				{
					if (Modifier.isAbstract(method.getModifiers()))
					{
						continue;
					}
					if (!nameJudger.judge(method.getName()))
					{
						continue;
					}

					XposedBridge.hookMethod(method, Hook.printName);
					log("hooked " + method.getDeclaringClass().getName() + "." + method.getName() + "()");
				}
				hookedClassList.add(workClass.getName());

				workClass = workClass.getSuperclass();
			}

			log("classes hooked: " + hookedClassList.size());
			log("-");
		}
	}

	protected void hookAllClass(Class<?> clazz)
	{
		Method[] methods = clazz.getMethods();// clazz.getDeclaredMethods()
		for (Method method : methods)
		{
			if (Modifier.isAbstract(method.getModifiers()))
			{
				continue;
			}

			if (method.getDeclaringClass().equals(Object.class))
			{
				continue;
			}
			XposedBridge.hookMethod(method, Hook.printName);
			log("hooked " + clazz.getName() + " ." + method.getName() + "()");
		}
		log("-");
	}

	protected void hookLooper(Class<?> clazz)
	{
		if (Handler.class.isAssignableFrom(clazz))
		{
			log("hooking " + clazz);

			Method method = null;
			try
			{
				method = clazz.getMethod("dispatchMessage", Message.class);
				// enqueueMessage
			}
			catch (NoSuchMethodException e)
			{
				e.printStackTrace();
			}

			XposedBridge.hookMethod(method, new XC_MethodHook()
			{
				protected void beforeHookedMethod(MethodHookParam param) throws Throwable
				{
					if (param.args[0] == null)
					{
						return;
					}
					Message msg = (Message) param.args[0];
					Field targetField = Message.class.getDeclaredField("target");
					Field callbackField = Message.class.getDeclaredField("callback");

					targetField.setAccessible(true);
					callbackField.setAccessible(true);

					Handler target = (Handler) targetField.get(msg);
					Runnable callback = (Runnable) callbackField.get(msg);
					log("> Dispatching " + target.getClass().getName());
					log("  Message:    " + msg.what + ", " + target.getMessageName(msg));
					log("  Callback:   " + callback);

					log("-");
				}

				protected void afterHookedMethod(MethodHookParam param) throws Throwable
				{
					if (param.args[0] == null)
					{
						return;
					}
					Message msg = (Message) param.args[0];
					Field targetField = Message.class.getDeclaredField("target");
					targetField.setAccessible(true);
					Handler target = (Handler) targetField.get(msg);

					log("< Finished    " + target.getClass().getName());
					log("-");
				}
			});
			log("hooked " + clazz.getName() + " .dispatchMessage()");
		}
	}

	
	
	
	protected void hookClick(Class<?> clazz)
	{
		if (!Activity.class.isAssignableFrom(clazz))
		{
			return;
		}

		XposedHelpers.findAndHookMethod(Activity.class, "dispatchTouchEvent", MotionEvent.class, GestureHooks.dispatchTouchEventHook);
	}
	
	protected void hookOnConfigurationChanged(Class<?> clazz)
	{
		if (!Activity.class.isAssignableFrom(clazz))
		{
			return;
		}

		XposedHelpers.findAndHookMethod(Activity.class, "onConfigurationChanged", Configuration.class, Hook.printName);
	}
}
